﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Thauan_Juliano_World
{
    class AEstrela
    {
        public List<Node> openList;
        public List<Node> closedList;
        public List<Node> scanned;
        public List<Node> obstacleList;

        public void SetObstacle(List<Node> obstacleList)
        {
            this.obstacleList = obstacleList;
        }

        public Node origin;
        public Node goal;

        public bool isForSearch = true;

        public AEstrela()
        {
            this.openList = new List<Node>();
            this.closedList = new List<Node>();
            this.scanned = new List<Node>();
            this.obstacleList = new List<Node>();
        }


        // Heuristica
        // Pegamos Node com menor F
        // Caso haja empate, pegamos o mais próximo do goal
        public Node GetBest(List<Node> openList)
        {
            if (this.openList.Count != 0)
            {
                Node bestNode = openList[0];

                foreach (Node node in openList)
                {
                    if (node.f < bestNode.f)
                    {
                        bestNode = node;
                    }
                }

                return bestNode;
            }
            else
            {
                isForSearch = false;
                return null;
            }
        }

        public void Search(Node origin, Node goal, ref Grid grid)
        {
            for (int i = 0; i < grid.Nodes.GetLength(0); i++)
            {
                for (int j = 0; j < grid.Nodes.GetLength(1); j++)
                {
                    grid.Nodes[i, j].plano.ChangeType(PlaneType.Normal);
                }
            }

            this.openList.Clear();
            this.closedList.Clear();
            this.scanned.Clear();

            this.origin = origin;
            this.goal = goal;

            this.isForSearch = true;

            if (this.obstacleList.Contains(this.goal))
                this.obstacleList.Remove(this.goal);

            this.openList.Add(origin);

            while (isForSearch)
            {
                SearchOpenList(this.openList, goal, grid);
            }
        }

        private void SearchOpenList(List<Node> openList, Node goal, Grid grid)
        {

            if (openList.Count != 0 && this.GetBest(this.openList) != null)
            {
                verificarAoRedor(this.GetBest(this.openList), goal, grid);
            }
            else
            {
                isForSearch = false;
            }
        }

        public bool isInOpenList(Node testNode)
        {
            if (this.openList.Contains(testNode))
            {
                return true;
            }

            return false;
        }

        private void Atributte(Node node, Node goal)
        {
            Node father = node.Father;

            int distanciaY = Math.Abs(goal.I - node.I);
            int distanciaX = Math.Abs(goal.J - node.J);

            int h = (distanciaX + distanciaY) * 2;

            node.g = father.g + Node.AmountG;
            Console.WriteLine("g: " + node.g.ToString() + " h: " + h.ToString());
            node.f = h + node.g;
        }

        private void verificarAoRedor(Node timeNode, Node goal, Grid grid)
        {
            if (timeNode.I != 0)
            {
                if (!this.obstacleList.Contains(grid.Nodes[timeNode.I - 1, timeNode.J]))
                {

                    if (grid.Nodes[timeNode.I - 1, timeNode.J].Equals(goal))
                    {
                        this.isForSearch = false;
                        goal.Father = timeNode;
                        Conclui(goal);
                        return;

                    }
                    else if (!this.isInOpenList(grid.Nodes[timeNode.I - 1, timeNode.J]) && !this.scanned.Contains(grid.Nodes[timeNode.I - 1, timeNode.J]))
                    {
                        Node node = grid.Nodes[timeNode.I - 1, timeNode.J];
                        node.Father = timeNode;
                        Atributte(node, goal);
                        openList.Add(node);
                    }
                }
            }

            if (timeNode.I < grid.Row - 1)
            {
                if (!this.obstacleList.Contains(grid.Nodes[timeNode.I + 1, timeNode.J]))
                {
                    if (grid.Nodes[timeNode.I + 1, timeNode.J].Equals(goal))
                    {
                        this.isForSearch = false;
                        goal.Father = timeNode;
                        Conclui(goal);
                        return;

                    }
                    else if (!this.isInOpenList(grid.Nodes[timeNode.I + 1, timeNode.J]) && !this.scanned.Contains(grid.Nodes[timeNode.I + 1, timeNode.J]))
                    {
                        grid.Nodes[timeNode.I + 1, timeNode.J].Father = timeNode;
                        Atributte(grid.Nodes[timeNode.I + 1, timeNode.J], goal);
                        openList.Add(grid.Nodes[timeNode.I + 1, timeNode.J]);
                    }
                }
            }

            if (timeNode.J != 0)
            {
                if (!this.obstacleList.Contains(grid.Nodes[timeNode.I, timeNode.J - 1]))
                {
                    if (grid.Nodes[timeNode.I, timeNode.J - 1].Equals(goal))
                    {
                        this.isForSearch = false;
                        goal.Father = timeNode;
                        Conclui(goal);
                        return;

                    }
                    else if (!this.isInOpenList(grid.Nodes[timeNode.I, timeNode.J - 1]) && !this.scanned.Contains(grid.Nodes[timeNode.I, timeNode.J - 1]))
                    {
                        grid.Nodes[timeNode.I, timeNode.J - 1].Father = timeNode;
                        Atributte(grid.Nodes[timeNode.I, timeNode.J - 1], goal);
                        openList.Add(grid.Nodes[timeNode.I, timeNode.J - 1]);
                    }
                }

            }

            if (timeNode.J < grid.Column - 1)
            {
                if (!this.obstacleList.Contains(grid.Nodes[timeNode.I, timeNode.J + 1]))
                {
                    if (grid.Nodes[timeNode.I, timeNode.J + 1].Equals(goal))
                    {
                        this.isForSearch = false;
                        goal.Father = timeNode;
                        Conclui(goal);
                        return;

                    }
                    else if (!this.isInOpenList(grid.Nodes[timeNode.I, timeNode.J + 1]) && !this.scanned.Contains(grid.Nodes[timeNode.I, timeNode.J + 1]))
                    {
                        grid.Nodes[timeNode.I, timeNode.J + 1].Father = timeNode;
                        Atributte(grid.Nodes[timeNode.I, timeNode.J + 1], goal);
                        openList.Add(grid.Nodes[timeNode.I, timeNode.J + 1]);

                    }
                }
            }

            this.openList.Remove(timeNode);
            this.scanned.Add(timeNode);
        }

        private void Conclui(Node goal) 
        {
            GeneratePath(goal);

            for (int i = 0; i < this.obstacleList.Count; i++)
            {
                this.obstacleList[i].plano.ChangeType(PlaneType.Obstacle);
            }

            for (int i = 0; i < this.scanned.Count; i++)
            {
                this.scanned[i].plano.ChangeType(PlaneType.Scanned);
            }

            for (int i = 0; i < this.closedList.Count; i++)
            {
                this.closedList[i].plano.ChangeType(PlaneType.Path);  
            }

            this.goal.plano.ChangeType(PlaneType.Goal);
            this.origin.plano.ChangeType(PlaneType.Origin);

        }

        private void GeneratePath(Node goal)
        {
            this.closedList.Add(goal);
            Console.WriteLine(this.closedList.Count);

            if (goal.Father != null && goal.Father != this.origin)
            {
                GeneratePath(goal.Father);
            }
        }
    }
}
